﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using MathematicallySafe.HeadCounter.Data.Common;
using MathematicallySafe.HeadCounter.Data.Models;

namespace MathematicallySafe.HeadCounter.Data
{
    public class HeadCounterService: IHeadCounterService, IDisposable
    {
        #region Filename and directory constants

        public static bool FirstTimeRun
        {
            get { return !File.Exists(Constants.OptionsFileName); }
        }

        #endregion

        public Guild Guild { get; private set; }
        public RaidHistory RaidHistory { get; private set; }
        public LootTable LootTable { get; private set; }
        public Options Options { get; private set; }
        public UserOptions UserOptions { get; private set; }

        public bool IsDirty { get; private set; }
        public bool IsClean { get { return !IsDirty; } }

        public void SetClean()
        {
            IsDirty = false;
        }

        public void SetDirty()
        {
            IsDirty = true;
        }

        #region LOAD

        public void Create(Options options, UserOptions userOptions)
        {
            Options = options;
            UserOptions = userOptions;
            
            Guild = new Guild(options.GuildName);
            Guild.SubscribeEvents();

            RaidHistory = new RaidHistory();

            LootTable = new LootTable();
            LootTable.SubscribeEvents();

            Save();
        }

        public void Load()
        {
            LoadOptions();

            LoadGuild();
            Guild.SubscribeEvents();

            LoadRaids();

            LoadLootTable();
            LootTable.ProcessLootOverrides();
            LootTable.SubscribeEvents();

            SubscribeEvents();
            IsDirty = false;
        }

        private void LoadGuild()
        {
            if (!File.Exists(Constants.GuildFileName)) throw new FileNotFoundException("Could not find guild file " + Constants.GuildFileName);
            DoGuildLoad();
        }

        private void DoGuildLoad()
        {
            var xmlSerializer = new XmlSerializer(typeof (Guild));

            using (Stream s = File.OpenRead(Constants.GuildFileName))
            {
                Guild = (Guild) xmlSerializer.Deserialize(s);
            }
        }

        private void LoadOptions()
        {
            if (!File.Exists(Constants.OptionsFileName)) throw new FileNotFoundException("Could not find options file " + Constants.OptionsFileName);
            if (!File.Exists(Constants.UseroptionsFileName))
            {
                var userOptions = new UserOptions
                                      {
                                          HeadCounterPath = @"C:\World of Warcraft\Interface\AddOns\Broker_HeadCounter"
                                      };

                UserOptions = userOptions;
                SaveUserOptions();
            }

            DoOptionsLoad();
            DoUserOptionsLoad();
        }

        private void DoOptionsLoad()
        {
            var xmlSerializer = new XmlSerializer(typeof (Options));

            using (Stream s = File.OpenRead(Constants.OptionsFileName))
            {
                Options = (Options) xmlSerializer.Deserialize(s);
                Options.AddDefaultLootOverrides();
            }
        }

        private void DoUserOptionsLoad()
        {
            var xmlSerializer = new XmlSerializer(typeof (UserOptions));

            using (Stream s = File.OpenRead(Constants.UseroptionsFileName))
            {
                UserOptions = (UserOptions) xmlSerializer.Deserialize(s);
            }
        }

        private void LoadRaids()
        {
            if (!File.Exists(Constants.RaidsFileName)) throw new FileNotFoundException("Could not find raids file " + Constants.RaidsFileName);
            DoRaidsLoad();
        }

        private void DoRaidsLoad()
        {
            var xmlSerializer = new XmlSerializer(typeof (RaidHistory));

            using (Stream s = File.OpenRead(Constants.RaidsFileName))
            {
                RaidHistory = (RaidHistory) xmlSerializer.Deserialize(s);
            }
        }

        private void LoadLootTable()
        {
            if (!File.Exists(Constants.LoottableFileName)) throw new Exception("Couldn't find loot table " + Constants.LoottableFileName);
            DoLootTableLoad();
        }

        private void DoLootTableLoad()
        {
            var xmlSerializer = new XmlSerializer(typeof (LootTable));

            using (Stream s = File.OpenRead(Constants.LoottableFileName))
            {
                LootTable = (LootTable) xmlSerializer.Deserialize(s);
            }
        }

        #endregion

        #region SAVE

        public void Save()
        {
            SaveOptions();
            SaveUserOptions();
            SaveGuild();
            SaveRaids();
            SaveLootTable();

            SetClean();
        }

        private void SaveOptions()
        {
            var xmlSerializer = new XmlSerializer(typeof (Options));

            using (Stream s = File.Create(Constants.OptionsFileName))
            {
                xmlSerializer.Serialize(s, Options);
            }
        }

        private void SaveUserOptions()
        {
            var xmlSerializer = new XmlSerializer(typeof (UserOptions));

            using (Stream s = File.Create(Constants.UseroptionsFileName))
            {
                xmlSerializer.Serialize(s, UserOptions);
            }
        }

        private void SaveGuild()
        {
            var xmlSerializer = new XmlSerializer(typeof (Guild));

            using (Stream s = File.Create(Constants.GuildFileName))
            {
                xmlSerializer.Serialize(s, Guild);
            }
        }

        private void SaveRaids()
        {
            var xmlSerializer = new XmlSerializer(typeof (RaidHistory));

            using (Stream s = File.Create(Constants.RaidsFileName))
            {
                xmlSerializer.Serialize(s, RaidHistory);
            }
        }

        private void SaveLootTable()
        {
            // Code to fix mistakes in loot tables
            //LootTable = GenerateFixedLootTable();

            var xmlSerializer = new XmlSerializer(typeof(LootTable));

            using (Stream s = File.Create(Constants.LoottableFileName))
            {
                xmlSerializer.Serialize(s, LootTable);
            }
        }

        private LootTable GenerateFixedLootTable()
        {
            var fixedLootTable = new LootTable();

            var items = (from item in LootTable.Items
                         orderby item.Zone , item.Boss , item.Name
                         select item.ID).Distinct();

            items.ForEach(item => fixedLootTable.Items.Add(LootTable.Items.First(i => i.ID == item)));

            return fixedLootTable;
        }

        #endregion

        #region events

        private void SubscribeEvents()
        {
            Guild.RaiderAdded += GuildRaiderAdded;
            Guild.RaiderRemoved += GuildRaiderRemoved;
            RaidHistory.RaidAdded += RaidHistoryRaidAdded;
            RaidHistory.RaidRemoved += RaidHistoryRaidRemoved;
        }

        private void UnsubscribeEvents()
        {
            Guild.RaiderAdded -= GuildRaiderAdded;
            Guild.RaiderRemoved -= GuildRaiderRemoved;
            RaidHistory.RaidAdded -= RaidHistoryRaidAdded;
            RaidHistory.RaidRemoved -= RaidHistoryRaidRemoved;
        }

        void GuildRaiderAdded(object sender, RosterChangedEventArgs e)
        {
            SetDirty();
        }

        void GuildRaiderRemoved(object sender, RosterChangedEventArgs e)
        {
            SetDirty();
        }

        void RaidHistoryRaidAdded(object sender, RaidsHistoryChangedEventArgs e)
        {
            SetDirty();
        }

        void RaidHistoryRaidRemoved(object sender, RaidsHistoryChangedEventArgs e)
        {
            var doomed = new List<string>();

            Guild.Raiders.ForEach(raider =>
            {
                var firstRaid = RaidHistory.Raids.Where(raid => raid.RaiderAttendedAnyChar(raider.Name)).FirstOrDefault();

                if (firstRaid == null)
                {
                    doomed.Add(raider.Identity);
                }
            });

            doomed.ForEach(d => Guild.RemoveRaider(d));
            SetDirty();
        }

        #endregion

        public void Dispose()
        {
            UnsubscribeEvents();
            LootTable.UnsubscribeEvents();
            Guild.UnsubscribeEvents();
        }
    }

    public class RaidersDataChangedEventArgs : EventArgs
    {
        public readonly IEnumerable<Raider> Raiders;

        public RaidersDataChangedEventArgs(IEnumerable<Raider> raiders)
        {
            Raiders = raiders;
        }
    }

}